﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Linq;
using CompactMapper.Orm.Contracts;
using CompactMapper.Orm.Extensions;
using CompactMapper.Orm.UnitOfWork.Commands.Interfaces;
using CompactMapper.Orm.UnitOfWork.Mappings;

namespace CompactMapper.Orm.UnitOfWork.Commands.Implementations
{
    internal sealed class InsertCommand : Command, IInsertCommand
    {
        private readonly String _commandText;
        private readonly Dictionary<Relationship, String> _joinTableInsertCommandTexts;

        public InsertCommand(Table table)
            : base(table)
        {
            _commandText = new SqlBuilder().Insert(Table).ToString();
            if (Table.Relationships.HasItems())
            {
                _joinTableInsertCommandTexts = new Dictionary<Relationship, string>();
                foreach (var relationship in Table.Relationships)
                {
                    _joinTableInsertCommandTexts.Add(relationship, new SqlBuilder().InsertJoinTable(relationship).ToString());
                }
            }
        }


        public Boolean Run(SQLiteConnection connection, IKeyedPersistent toPersist, String parentTableName, IKey parentKey)
        {
            if (toPersist == null)
                return false;

            var alreadyInserted = false;

            using (var cmd = new SQLiteCommand(_commandText, connection))
            {

                var relation = Table.GetRelationshipWith(parentTableName);
                if (relation == Relationship.None)
                {
                    var hadKey = toPersist.Key.IsSet();
                    if (!hadKey)
                    {
                        toPersist.Key = AbstractKeyFactory.For(toPersist.Key.Value.GetType()).New();
                    }
                    try
                    {
                        AddInsertParameters(cmd, toPersist);
                        cmd.ExecuteNonQuery();
                    }
                    catch
                    {
                        alreadyInserted = true;
                    }
                }
                else
                {
                    var hadKey = toPersist.Key.IsSet();
                    if (!hadKey)
                    {
                        toPersist.Key = AbstractKeyFactory.For(toPersist.Key.Value.GetType()).New();
                    }
                    try
                    {
                        AddInsertParameters(cmd, toPersist);
                        cmd.ExecuteNonQuery();
                    }
                    catch
                    {
                        alreadyInserted = true;
                    }
                    finally
                    {
                        RunWithRelation(connection, toPersist, relation, parentKey);
                    }
                }
            }
            return !alreadyInserted;
        }

        private void RunWithRelation(SQLiteConnection connection, IKeyedPersistent toPersist,
                                     Relationship relationship, IKey parentKey)
        {
            using (var aux = new SQLiteCommand
                                 {
                                     Connection = connection,
                                     CommandText = _joinTableInsertCommandTexts[relationship]
                                 })
            {
                aux.Parameters.Add(new SQLiteParameter(relationship.ParentTableName.ToSqlValue(),
                                                       parentKey.Value));
                aux.Parameters.Add(new SQLiteParameter(relationship.ChildTableName.ToSqlValue(),
                                                       toPersist.Key.Value));
                try
                {
                    aux.ExecuteNonQuery();
                }
                catch
                {
                }
            }
        }


        private void AddInsertParameters(SQLiteCommand command, IKeyedPersistent entity)
        {
            command.Parameters.Clear();
            var parameters = Table.Meta.Columns
                .Select(column => new SQLiteParameter(column.Name.ToSqlValue(), column.ToSqLiteObject(entity))).ToList();
            command.Parameters.AddRange(parameters.ToArray());
        }
    }
}